#pragma region Licensing
/*
       LOGL::GLib -> LorgonJortle's OpenGL Game Library
       Copyright (C) 2011  Jesse 'LorgonJortle' Wilkerson

       This program is free software: you can redistribute it and/or modify
       it under the terms of the GNU General Public License as published by
       the Free Software Foundation, either version 3 of the License, or
       (at your option) any later version.

       This program is distributed in the hope that it will be useful,
       but WITHOUT ANY WARRANTY; without even the implied warranty of
       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       GNU General Public License for more details.

       You should have received a copy of the GNU General Public License
       along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#pragma endregion

#include "Game.h"

namespace LOGL_GLib
{

     /******************************************************************************
      Game::Game();
               Arguments:
                    void

               Description:
                    Constructs the Game object.

               Returns:
                    The Game object.
     *******************************************************************************/
     Game::Game()
     {
          /* Window defaults. */
          windowWidth = 1280;
          windowHeight = 768;
          isFullscreen = GL_FALSE;

          /* Setup our default FPS and UPS. */
          IdealFPS(60);
          IdealUPS(60);
     }

     /******************************************************************************
      Game::~Game();
               Arguments:
                    void

               Description:
                    Deconstructs the Game object and free any indebted resources.

               Returns:
                    void
     *******************************************************************************/
     Game::~Game()
     {
          Cleanup();
     }

     /******************************************************************************
      GLvoid Game::Initialize();
               Arguments:
                    void

               Description:
                    Initializes essential variables and prepares for content loading
                    and the game loop.

               Returns:
                    void
     *******************************************************************************/
     GLvoid Game::Initialize()
     {
          /* Assume success (optimistic programming). */
          ExitCode = 0;

          /* Initialize SDL. */
          if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) < 0)
               throw Exception("SDL failed to initialize.");

          /* Give SDL some OpenGL attributes. */
          SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1 );
          SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
          SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
          SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
          SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
          SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);
          SDL_GL_SetAttribute(SDL_GL_BUFFER_SIZE, 32);

          SDL_GL_SetAttribute(SDL_GL_ACCUM_RED_SIZE, 8);
          SDL_GL_SetAttribute(SDL_GL_ACCUM_GREEN_SIZE, 8);
          SDL_GL_SetAttribute(SDL_GL_ACCUM_BLUE_SIZE, 8);
          SDL_GL_SetAttribute(SDL_GL_ACCUM_ALPHA_SIZE, 8);

          /* Create our rendering context. */
          screen = SDL_SetVideoMode(windowWidth, windowHeight, 32, SDL_OPENGL);
          if(!screen)
               throw Exception("SDL failed to create an OpenGL context.");

          /* Get OpenGL going. */
          InitializeGL();

          /* Get font loading going. */
          if (TTF_Init() < 0)
               throw Exception("SDL_TTF failed to initialize.");

          /* Get batch rendering going. */
          if(!SpriteBatch::Initialize(Shaders))
               throw Exception("Failed to setup batch rendering.");

          /* Get the keyboard going. */
          Keyboard::Initialize();

          /* Get exception handling going. */
          Exception::Initialize(&lastError);
     }

     /******************************************************************************
      GLvoid Game::InitializeGL();
               Arguments:
                    void

               Description:
                    Gets OpenGL up and running.

               Returns:
                    void
     *******************************************************************************/
     GLvoid Game::InitializeGL()
     {
          /* Get GLEW up and running. */
          if(glewInit() != GLEW_OK)
               throw Exception("Failed to initialize GLEW.");

          glShadeModel(GL_SMOOTH);
          glClearColor(0.0f, 0.0f, 0.0f, 0.5f);
          glClearDepth(1.0f);
          glEnable(GL_DEPTH_TEST);
          glDepthFunc(GL_LEQUAL);
          glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
     }

     /******************************************************************************
      GLvoid Game::LoadContent();
               Arguments:
                    void

               Description:
                    Loads any content that will be used for the game.

               Returns:
                    void
     *******************************************************************************/
     GLvoid Game::LoadContent()
     {
     }

     /******************************************************************************
      GLvoid Game::Run();
               Arguments:
                    void

               Description:
                    Runs the game loops and automates the game.

               Returns:
                    void
     *******************************************************************************/
     GLvoid Game::Run()
     {
          try
          {
               /* Before we run, let's initialize everything. */
               Initialize();
               /* Load up any content needed for the game. */
               LoadContent();

               /* Run the game. */
               SDL_Event event;
               GLboolean done = GL_FALSE;
               while(!done)
               {
                    while(SDL_PollEvent(&event))
                    {
                        switch(event.type)
                        {
                             /* SDL says to quit. */
                             case SDL_QUIT:
                             {
                                   done = GL_TRUE;
                             } break;

                              /* A key has been pressed. */
                             case SDL_KEYDOWN:
                             {
                                  Keyboard::KeyDown(event.key.keysym.sym);
                             } break;

                              /* A pressed key has been lifted. */
                             case SDL_KEYUP:
                             {
                                   Keyboard::KeyUp(event.key.keysym.sym);
                             } break;

                              /* A mouse button has been pressed. */
                              case SDL_MOUSEBUTTONDOWN:
                              {
                                   Mouse::Click(event.button.button, Mouse_Pressed);
                              } break;

                              /* A pressed mouse button has been lifted. */
                              case SDL_MOUSEBUTTONUP:
                              {
                                   Mouse::Click(event.button.button, Mouse_Released);
                              } break;

                              /* The mouse has moved from one position to another on the screen. */
                              case SDL_MOUSEMOTION:
                              {
                                   Mouse::Motion(event.motion.x, event.motion.y, event.motion.xrel, event.motion.yrel);
                              } break;

                        }
                    }

                    /* Draw and update if it's time to. */
                    if(LimitUPS() == GL_TRUE)
                         Update();
                    if(LimitFPS() == GL_TRUE)
                         Draw();
               }

          }
          catch(Exception e)
          {
               /* We have thrown an unhandled exception in the game. */
               std::cout << "User exception thrown: " << e.what();
          }
          catch(std::exception& e)
          {
               /* A C++ runtime error has occurred. */
               std::cout << "Runtime error occurred: " << e.what();
          }
     }

     /******************************************************************************
      std::string Game::GetLastError() const;
               Arguments:
                    void

               Description:
                    Gets the last error to occur in the program.

               Returns:
                    The last error to occur in the program.
     *******************************************************************************/
     std::string Game::GetLastError() const
     {
          return lastError;
     }

     /******************************************************************************
      GLvoid Game::Update();
               Arguments:
                    void

               Description:
                    Updates under-the-hood game logic and any components.

               Returns:
                    void
     *******************************************************************************/
     GLvoid Game::Update()
     {
          Keyboard::SwapBuffers();
          Mouse::SwapBuffers();

          updateSpeed.Tick();
     }

     /******************************************************************************
      GLvoid Game::Draw();
               Arguments:
                    void

               Description:
                    Handles low-level drawing and renders any components
                    in the component list.

               Returns:
                    void
     *******************************************************************************/
     GLvoid Game::Draw()
     {
          frameSpeed.Tick();

          glFlush();
          SDL_GL_SwapBuffers();
     }

     /******************************************************************************
      GLvoid Game::LimitFPS();
               Arguments:
                    void

               Description:
                    Keeps the game drawing at the ideal FPS.

               Returns:
                    void
     *******************************************************************************/
     GLboolean Game::LimitFPS()
     {
          static GLfloat lastTime = 0.0f;
          GLfloat currentTime = SDL_GetTicks() * 0.001f;
          if((currentTime - lastTime) > (1.0f / idealFPS))
          {
               lastTime = currentTime;
               return GL_TRUE;
          }
          else
               return GL_FALSE;
     }

     /******************************************************************************
      GLvoid Game::LimitUPS();
               Arguments:
                    void

               Description:
                    Keeps the game updating at the ideal UPS.

               Returns:
                    void
     *******************************************************************************/
     GLboolean Game::LimitUPS()
     {
          static GLfloat lastTime = 0.0f;
          GLfloat currentTime = SDL_GetTicks() * 0.001f;
          if(currentTime - lastTime > (1.0f / idealUPS))
          {
               lastTime = currentTime;
               return GL_TRUE;
          }
          else
               return GL_FALSE;
     }

     /******************************************************************************
      GLuint Game::CurrentFPS();
               Arguments:
                    void

               Description:
                    Gets the current FPS of the game.

               Returns:
                    The current FPS of the game.
     *******************************************************************************/
     GLuint Game::CurrentFPS()
     {
          return frameSpeed.Frequency;
     }

     /******************************************************************************
      GLuint Game::IdealFPS();
               Arguments:
                    void

               Description:
                    Gets the ideal FPS of the game.

               Returns:
                    The ideal FPS of the game.
     *******************************************************************************/
     GLuint Game::IdealFPS()
     {
          return idealFPS;
     }

     /******************************************************************************
      GLvoid Game::IdealFPS(GLuint _IdealFPS);
               Arguments:
                    GLuint _IdealFPS:
                         The new ideal FPS for the game.

               Description:
                    Sets the ideal FPS of the game.

               Returns:
                    void
     *******************************************************************************/
     GLvoid Game::IdealFPS(GLuint _IdealFPS)
     {
          idealFPS = _IdealFPS;
     }

     /******************************************************************************
      GLuint Game::CurrentUPS();
               Arguments:
                    void

               Description:
                    Gets the current UPS of the game.

               Returns:
                    The current UPS of the game.
     *******************************************************************************/
     GLuint Game::CurrentUPS()
     {
          return updateSpeed.Frequency;
     }

     /******************************************************************************
      GLuint Game::IdealUPS();
               Arguments:
                    void

               Description:
                    Gets the ideal UPS of the game.

               Returns:
                    The ideal UPS of the game.
     *******************************************************************************/
     GLuint Game::IdealUPS()
     {
          return idealUPS;
     }

     /******************************************************************************
      GLvoid Game::IdealUPS(GLuint _IdealUPS);
               Arguments:
                    GLuint _IdealUPS:
                         The new ideal UPS for the game.

               Description:
                    Sets the ideal UPS of the game.

               Returns:
                    void
     *******************************************************************************/
     GLvoid Game::IdealUPS(GLuint _IdealUPS)
     {
          idealUPS = _IdealUPS;
     }

     /******************************************************************************
      GLboolean Game::ToggleFullscreen();
               Arguments:
                    void

               Description:
                    Toggles fullscreen mode.

               Returns:
                    Whether or not the new video mode is enabled.
     *******************************************************************************/
     GLboolean Game::ToggleFullscreen()
     {
          /* Change the video mode. */
          if(isFullscreen)
               screen = SDL_SetVideoMode(windowWidth, windowHeight, 32, SDL_OPENGL);
          else
               screen = SDL_SetVideoMode(windowWidth, windowHeight, 32, SDL_OPENGL | SDL_FULLSCREEN);

          /* Negate our flag. */
          isFullscreen = !isFullscreen;

          /* Return whether or not we still have a screen. */
          return screen == 0;
     }

     /******************************************************************************
      GLvoid Game::ResizeWindow(GLint _Width, GLint _Height);
               Arguments:
                    GLint _Width:
                         The new width of the game window.
                    GLint _Height:
                         The new height of the game window.

               Description:
                    Resizes the game window to the specified dimensions.

               Returns:
                    void
     *******************************************************************************/
     GLvoid Game::ResizeWindow(GLint _Width, GLint _Height)
     {
          /* Only bother if the specified values are greater than 0. */
          if(_Width > 0 && _Height > 0)
          {
               /* Store our new values. */
               windowWidth = _Width;
               windowHeight = _Height;

               /* Resize the window. */
               if(isFullscreen)
                    screen = SDL_SetVideoMode(windowWidth, windowHeight, 32, SDL_OPENGL | SDL_FULLSCREEN);
               else
                    screen = SDL_SetVideoMode(windowWidth, windowHeight, 32, SDL_OPENGL);
          }
     }

     /******************************************************************************
      SDL_Surface* Game::Screen() const;
               Arguments:
                    void

               Description:
                    Gets a pointer to the game's screen.

               Returns:
                    A pointer to the game's screen.
     *******************************************************************************/
     SDL_Surface* Game::Screen() const
     {
          return screen;
     }

     /******************************************************************************
      GLint Game::ScreenWidth() const;
               Arguments:
                    void

               Description:
                    Gets the screen's width.

               Returns:
                    The screen's width.
     *******************************************************************************/
     GLint Game::ScreenWidth() const
     {
          return windowWidth;
     }

     /******************************************************************************
      GLint Game::ScreenHeight() const;
               Arguments:
                    void

               Description:
                    Gets the screen's height.

               Returns:
                    The screen's height.
     *******************************************************************************/
     GLint Game::ScreenHeight() const
     {
          return windowHeight;
     }

     /******************************************************************************
      GLvoid Game::UnloadContent();
               Arguments:
                    void

               Description:
                    Releases any indebted content resources and prepares for Cleanup.

               Returns:
                    void
     *******************************************************************************/
     GLvoid Game::UnloadContent()
     {
     }

     /******************************************************************************
      GLvoid Game::Cleanup();
               Arguments:
                    void

               Description:
                    Releases any indebted resources and cleans up before closing.

               Returns:
                    void
     *******************************************************************************/
     GLvoid Game::Cleanup()
     {
          /* Unload any loaded content. */
          UnloadContent();

          /* Quit SDL_TTF. */
          TTF_Quit();

          /* Cleanup our batch rendering. */
          SpriteBatch::Cleanup();

          /* Quit SDL. */
          SDL_Quit();
     }

     /******************************************************************************
      GLvoid Game::Exit();
               Arguments:
                    void

               Description:
                    Pushes a quit event to SDL.

               Returns:
                    void
     *******************************************************************************/
     GLvoid Game::Exit()
     {
          /* Push a quit event. */
          SDL_Event quit;
          quit.type = SDL_QUIT;
          SDL_PushEvent(&quit);
     }

} /*Namespace LOGL_GLib. */
